WebGL 鼠标与触摸交互映射
一句话结论:交互的关键是坐标换算,把屏幕像素坐标正确映射到裁剪坐标,拖拽与缩放才有正确的手感。
鼠标与触摸交互映射
是什么
鼠标与触摸交互映射,是把浏览器事件的屏幕像素坐标转换为 WebGL 裁剪坐标(x 与 y 均在 -1 到 1 之间)、再据此驱动相机或物体参数的过程。
解决什么问题
它解决点击位置与绘制位置对不上的问题。浏览器事件的原点在左上角、Y 轴向下、单位是 CSS 像素;WebGL 裁剪坐标原点在中心、Y 轴向上、范围只有 -1 到 1,两者不一致时必须显式换算。
为什么这样做
事件系统属于 DOM,渲染属于 GPU,二者坐标系天然不同。换算只需一次减偏移与两次线性变换,成本可忽略;把它做成独立函数还能同时服务鼠标、触摸与指针事件,避免多处重复实现。
怎么用
JavaScript
function toClipX(canvas, clientX, clientY) {
const rect = canvas.getBoundingClientRect();
// 相对画布的 CSS 像素坐标
const px = clientX - rect.left;
const py = clientY - rect.top;
return {
x: (px / rect.width) * 2 - 1,
y: 1 - (py / rect.height) * 2, // Y 轴翻转
};
}
// 拖拽旋转:位移映射为角度增量
let last = null, rotX = 0.6, rotY = 0.8;
canvas.addEventListener('pointerdown', (e) => { last = { x: e.clientX, y: e.clientY }; });
canvas.addEventListener('pointermove', (e) => {
if (!last) return;
rotY += (e.clientX - last.x) * 0.01;
rotX += (e.clientY - last.y) * 0.01;
rotX = Math.max(-1.5, Math.min(1.5, rotX)); // 限制俯仰,避免翻转
last = { x: e.clientX, y: e.clientY };
render();
});
canvas.addEventListener('pointerup', () => { last = null; });
// 滚轮缩放:改变相机距离
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
distance = Math.max(1.2, Math.min(20, distance + e.deltaY * 0.01));
render();
}, { passive: false });
常见坑
- 直接用 clientX 减 canvas.width 计算 → 原因:画布宽高是渲染缓冲尺寸,不是页面位置 → 处理:用 getBoundingClientRect 求偏移与 CSS 尺寸
- 忘记翻转 Y 轴 → 原因:两套坐标系 Y 方向相反 → 处理:用 1 减去归一化后的值
- 拖拽时画面翻转 → 原因:俯仰角越界 → 处理:把角度限制在正负 90 度以内
- 移动端页面跟着滑动 → 原因:未阻止默认行为 → 处理:监听时设置 passive 为 false 并调用 preventDefault
- 滚轮事件报错 → 原因:被动监听器不允许 preventDefault → 处理:显式传 passive false
要点总结
- 坐标换算三步:减去画布偏移、按 CSS 尺寸归一化、翻转 Y 轴
- 用 getBoundingClientRect 取偏移与尺寸,不要用 canvas.width/height
- 拖拽旋转把屏幕位移按比例映射为角度增量,并限制俯仰角
- 滚轮缩放用 passive false 的监听器才能阻止页面滚动
- 指针事件可统一鼠标与触摸,减少分支代码
